/*
 * graph2D
 * Copyright (c) 2009 Shun Moriya <shun@mnu.sakura.ne.jp>
 *
 * The MIT License
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
 * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR
 * THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

#if !defined(___GRAPH2D_GRAPHIC_DEVICE_H___)
#define ___GRAPH2D_GRAPHIC_DEVICE_H___

#include "common.h"
#include "color.h"
#include "rect.h"
#include "vector2.h"

#if defined(TARGET_WINDOWS)
 #include <windows.h>
 #include <gl/gl.h>
 #include <gl/glu.h>
#elif defined(TARGET_IPHONE)
 #include <OpenGLES/ES1/gl.h>
 #include <OpenGLES/ES1/glext.h>
#endif

#if (defined(DEBUG) || defined(_DEBUG) || defined(_DEBUG_)) || !defined(NDEBUG)
#include <libmana.h>
#define GRAPH2D_CHECK_GL_ERROR() {		\
		GLenum error = glGetError();	\
		if(error != GL_NO_ERROR){		\
			MANA_ERROR("%s(%d): OpenGL Error #%x\n", __FILE__, __LINE__, error); \
		}								\
	}
#define GRAPH2D_CALL_GL(FUNC) {		\
		FUNC;						\
		GRAPH2D_CHECK_GL_ERROR();	\
	}
#else
#define GRAPH2D_CHECK_GL_ERROR() ((void)0)
#define GRAPH2D_CALL_GL(FUNC) FUNC
#endif

namespace Graph2D
{
	class GraphicDevice
	{
	public:
#if defined(TARGET_WINDOWS) || defined(TARGET_APPLE) || defined(TARGET_LINUX)
		static const unsigned int LINE_LOOP = GL_LINE_LOOP;
		static const unsigned int TRIANGLES = GL_TRIANGLES;
		static const unsigned int TRIANGLE_STRIP = GL_TRIANGLE_STRIP;
#else
		static const unsigned int LINE_LOOP = 0;
		static const unsigned int TRIANGLES = 0;
		static const unsigned int TRIANGLE_STRIP = 0;
#endif

		static const unsigned char TYPE_OPAQUE             = 0x01;
		static const unsigned char TYPE_SCISSOR            = 0x02;
		static const unsigned char TYPE_COLOR              = 0x40;
		static const unsigned char TYPE_TEXTURE_COORDINATE = 0x80;

		typedef enum BlendMode
		{
			BLEND_MODE_NORMAL = 0,
			BLEND_MODE_ADD,
			BLEND_MODE_MULTI,
			BLEND_MODE_SCREEN,
		}BlendMode;

		typedef enum AlphaFunc
		{
			ALPHA_FUNC_NEVER = 0,
			ALPHA_FUNC_LESS,
			ALPHA_FUNC_LEQUAL,
			ALPHA_FUNC_EQUAL,
			ALPHA_FUNC_NOTEQUAL,
			ALPHA_FUNC_GEQUAL,
			ALPHA_FUNC_GREATER,
			ALPHA_FUNC_ALWAYS
		}AlphaFunc;

	private:
		static const GLsizei VERTEX_MAX_COUNT = (8192 * 6);

		static GLubyte colors[4 * VERTEX_MAX_COUNT];
		static GLfloat coordinates[2 * VERTEX_MAX_COUNT];
		static GLfloat verteces[2 * VERTEX_MAX_COUNT];
		static GLsizei colorCount;
		static GLsizei coordinateCount;
		static GLsizei vertexCount;

		static Rect displayRect;
		static float displayToScreenScale;

		static Vector2 screenSize;
		static float screenToDisplayScale;

		static bool lastEnableAlpha;
		static bool lastEnableDepth;
		static bool lastEnableOpaque;
		static bool lastEnableScissor;
		static bool lastEnableTexture;
		static bool lastEnableColorArray;
		static bool lastEnableTextureCoordArray;
		static Vector2 lastEnableScissorPosition;
		static Vector2 lastEnableScissorSize;
		static Color lastPrimitiveColor;
		static GLenum lastPrimitiveMode;
		static float lastAlphaFuncValue;
		static unsigned char lastPrimitiveType;
		static unsigned char lastAlphaFunc;
		static unsigned char lastBlendMode;

	public:
		static void initialize(const Vector2& deviceSize, const Vector2& screenSize);
		static void finalize();

		static const Vector2& getScreenSize();
		static float getScreenWidth();
		static float getScreenHeight();
		static float getScreenToDisplayScale();

		static const Vector2& getDisplaySize();
		static float getDisplayWidth();
		static float getDisplayHeight();
		static float getDisplayToScreenScale();

		static Vector2 translateScreenPosition(const Vector2& displayPosition);
		static Vector2 translateDisplayPosition(const Vector2& screenPosition);

		static void applyDrawArrayPointer();

		static void viewport(const int x, const int y, const int width, const int height);

		static void frustum(const float fov, const float near, const float far);
		static void ortho2D();

		static void enableAlpha(const bool enable);
		static void enableDepth(const bool enable);
		static void enableOpaque(const bool enable);
		static void enableScissor(const bool enable);
		static void enableTexture(const bool enable);
		static void enableColorArray(const bool enable);
		static void enableTextureCoordArray(const bool enable);

		static void getScissor(Vector2& position, Vector2& size);
		static void setScissor(const Vector2& position, const Vector2& size);
		static void setClearColor(const Color& color);
		static const Color& getColor();
		static void setColor(const Color& color);
		static void setAlphaFunc(const AlphaFunc alphaFunc, const float value);
		static void setBlendMode(const BlendMode blendMode);

		static void clear();

		static void drawRect();

		static void begin(const unsigned char type, const GLenum mode, const Color& color);
		static void end();
		static void addColor(const Color& color);
		static void addTextureCoord(const Vector2& textureCoord);
		static void addVertex(const Vector2& vertex);
		static void flush();
		static void finish();
	};

	//! 描画環境
	typedef struct DrawRect
	{
		Vector2 position;				//!< 描画左上座標（スクリーン）
		Vector2 size;					//!< セルサイズ
		Vector2 scale;					//!< 描画スケール
		Vector2 clippingLeftTop;		//!< クリッピング左上座標（スクリーン）
		Vector2 clippingRightBottom;	//!< クリッピング右下座標（スクリーン）
		Vector2 scrollOffset;			//!< スクロールオフセット値
		Color color;					//!< 描画色

		DrawRect()
		{
			size = GraphicDevice::getScreenSize();
			scale.set(1, 1);
			color = Color::WHITE;
			updateClippingInfomation();
		}

		const Vector2& getPosition() const
		{
			return position;
		}

		const Vector2& getSize() const
		{
			return size;
		}

		const Vector2& getScale() const
		{
			return scale;
		}

		const Vector2& getScrollOffset() const
		{
			return scrollOffset;
		}

		void setScrollOffset(const Vector2& scrollOffset)
		{
			this->scrollOffset = scrollOffset;
		}

		Vector2 getCenter() const
		{
			return position + size * 0.5f;
		}

		const Color getColor() const
		{
			return color;
		}

		Vector2 getDrawLeftTopPosition() const
		{
			return position;
		}

		Vector2 getDrawRightBottomPosition() const
		{
			return getDrawLeftTopPosition() + getDrawSize();
		}

		Vector2 getDrawCenter() const
		{
			return position + getDrawSize() * 0.5f;
		}

		Vector2 getDrawSize() const
		{
			return size * scale;
		}

		void updateClippingInfomation()
		{
			clippingLeftTop = getDrawLeftTopPosition();
			clippingRightBottom = clippingLeftTop + getDrawSize();
		}

		bool updateClippingInfomation(const DrawRect& parentDrawRect)
		{
			updateClippingInfomation();

			if(clippingLeftTop.x < parentDrawRect.clippingLeftTop.x)
				clippingLeftTop.x = parentDrawRect.clippingLeftTop.x;
			if(clippingLeftTop.y < parentDrawRect.clippingLeftTop.y)
				clippingLeftTop.y = parentDrawRect.clippingLeftTop.y;

			if(clippingRightBottom.x > parentDrawRect.clippingRightBottom.x)
				clippingRightBottom.x = parentDrawRect.clippingRightBottom.x;
			if(clippingRightBottom.y > parentDrawRect.clippingRightBottom.y)
				clippingRightBottom.y = parentDrawRect.clippingRightBottom.y;

			return (clippingLeftTop != parentDrawRect.clippingLeftTop || clippingRightBottom != parentDrawRect.clippingRightBottom);
		}

		Vector2 getClippingSize() const
		{
			Vector2 wh = clippingRightBottom - clippingLeftTop;
			if(wh.x < 0)
				wh.x = 0;
			if(wh.y < 0)
				wh.y = 0;
			return wh;
		}

		Color getDrawColor(const Color& color) const
		{
			return this->color * color;
		}
	}DrawRect;
}

#endif
